/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 *
 */
public final class Strings {
    private Strings() {}

    private static final Random random = new Random();

    public static String[] shorts(String src) {
        String hex = Bytes.toHex(Secrets.md5(src));
        String[] arr = new String[4];
        for (int i = 0; i < 4; i++) {
            // 把hex按照8位一组16进制与0x3FFFFFFF 进行位与运算
            String sub = hex.substring(i * 8, i * 8 + 8);
            // 这里需要使用long型来转换，因为Inteper.parseInt()只能处理31位, 首位为符号位, 如果不用long，则会越界
            long hexLong = 0x3FFFFFFF & Long.parseLong(sub, 16);
            StringBuilder outChars = new StringBuilder();
            for (int j = 0; j < 6; j++) {
                // 把得到的值与0x0000003D进行位与运算，取得字符数组chars索引
                long index = 0x0000003D & hexLong;
                // 把取得的字符相加
                outChars.append(Radix62.ALPHABET[(int) index]);
                // 每次循环按位右移5位
                hexLong = hexLong >> 5;
            }
            // 把字符串存入对应索引的输出数组
            arr[i] = outChars.toString();
        }
        return arr;
    }

    public static String hash(String src) {
        StringBuilder sb = new StringBuilder();
        int hash = Math.abs(MurmurHash.hash32(src));
        while (hash > 0) {
            int index = hash % Radix62.ALPHABET.length;
            hash = hash / Radix62.ALPHABET.length;
            sb.insert(0, Radix62.ALPHABET[index]);
        }
        return sb.toString();
    }
    
    /**
     * 返回一个定长的随机字符串(只包含大小写字母、数字)
     * @param length 随机字符串长度
     * @return 随机字符串
     */
    public static String randomString(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(Radix62.ALPHABET[random.nextInt(Radix62.ALPHABET.length)]);
        }
        return sb.toString();
    }
    
    /**
     * 返回一个定长的随机纯字母字符串(只包含大小写字母)
     * @param length 随机字符串长度
     * @return 随机字符串
     */
    public static String randomLetter(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(Radix62.ALPHABET[10 + random.nextInt(Radix62.ALPHABET.length - 10)]);
        }
        return sb.toString();
    }

    /**
     * 生成一个定长的随机纯数字字符串
     * @param length 随机字符串长度
     * @return 随机字符串
     */
    public static String randomNumber(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(random.nextInt(10));
        }
        return sb.toString();
    }

    private static final char[] UNALIKE_CHARACTER_SET = "23456789ABDEFGHJLMNQRTYabdefghijmnqrty".toCharArray();

    /**
     * 生成一个容易辨认字符组成的定长随机字符串
     * @param length 随机字符串长度
     * @return 随机字符串
     */
    public static String randomUnalikeString(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(UNALIKE_CHARACTER_SET[random.nextInt(UNALIKE_CHARACTER_SET.length)]);
        }
        return sb.toString();
    }
    
    /**
     * 去除字符串前面空白
     * @param str 字符串
     * @return 返回去除字符串前面空白后的字符串
     */
    public static String ltrim(String str) {
        int len = str.length();
        int st = 0;
        char[] val = str.toCharArray();
        
        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        return (st > 0) ? str.substring(st, len) : str;
    }
    
    /**
     * 去除字符串后面空白
     * @param str 字符串
     * @return 返回去除字符串后面空白后的字符串
     */
    public static String rtrim(String str) {
        int len = str.length();
        char[] val = str.toCharArray();
        
        while ((0 < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return (len < str.length()) ? str.substring(0, len) : str;
    }
    
    /**
     * 将首字节大写。注意：此方法与JavaBean规范中定义的属性的getter、setter方法名称不同。
     * @param str 字符串
     * @return 返回首字节大写后的字符串
     */
    public static String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toTitleCase(str.charAt(0)) + str.substring(1);
    }
    
    /**
     * 将首字节小写。注意：此方法与JavaBean规范中定义的属性的getter、setter方法名称不同。
     * @param str 字符串
     * @return 返回首字节小写后的字符串
     */
    public static String uncapitalized(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return Character.toLowerCase(str.charAt(0)) + str.substring(1);
    }

    /**
     * @deprecated 使用 {@link #uncapitalized(String)}
     */
    @Deprecated
    public static String uncapitalize(String str) {
        return uncapitalized(str);
    }
    
    /**
     * 将驼峰式字符串转换为下划线小写方式。如果转换前的字符串为空，则返回空字符串。</br> 
     * 转换规格是：在大写字符前增加“_”并转为小写
     * 例如：helloWorld->hello_world
     * @param str 转换前的驼峰式字符串
     * @return 转换后下划线方式字符串
     */
    public static String underscore(String str) {
        if (str == null || str.trim().isEmpty()) {
            return "";
        }
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char ch = str.charAt(i);
            if (Character.isUpperCase(ch)) {
                sb.append("_");
                sb.append(Character.toLowerCase(ch));
            } else {
                sb.append(ch);
            }
        }
        return sb.toString();
    }
    
    /**
     * 将下划线方式字符串转为小驼峰式。如果转换前的字符串为空，则返回空字符串。</br>
     * 转换规格是：去掉“_”并大写其后的一个字符 
     * 例如：hello_world->helloWorld
     * @param str 转换前的下划线方式字符串
     * @return 转换后小驼峰式命名字符串
     */
    public static String camelCase(String str) {
        if (str == null || str.trim().isEmpty()) {
            return "";
        }
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char ch = str.charAt(i);
            if (ch == '_') {
                i++;
                if (i >= len) {
                    sb.append(ch);
                    break;
                }
                sb.append(Character.toUpperCase(str.charAt(i)));
            } else {
                sb.append(ch);
            }
        }
        return sb.toString();
    }

    /**
     * 使用 fmt 做模板格式化给定的参数，作用与 {@link MessageFormat#format(String, Object...)} 相同
     * @param fmt 模板
     * @param params 参数
     * @return 返回格式化后的字符串
     */
    public static String format(String fmt, final Object... params) {
        if (params != null && params.length > 0) {
            // MessageFormat 要求 It's 中的  ' 是 ''
            String pattern = fmt.replaceAll("'", "''");
            return MessageFormat.format(pattern, params);
        }
        return fmt;
    }

    /**
     * 将数组转为用指定分隔符分隔的字符串
     * @param array 数据
     * @param separator 分隔符
     * @return 返回用指定分隔符分隔的字符串
     */
    public static String toString(Object[] array, String separator) {
        StringBuilder sb = new StringBuilder();
        for (Object object : array) {
            if (sb.length() > 0) {
                sb.append(separator);
            }
            sb.append(object);
        }
        return sb.toString();
    }

    /**
     * 按指定长度，省略字符串部分字符
     *
     * @param str 字符串
     * @param length 保留字符串字数
     *
     * @return 省略后的字符串
     */
    public static String omitString(String str, int length) {
        if (str == null) {
            return null;
        }
        if (length <= 0) {
            return "";
        }
        if (str.length() <= length) {
            return str;
        }
        StringBuilder buf1 = new StringBuilder();
        StringBuilder buf2 = new StringBuilder();
        int i1 = 0;
        int i2 = str.length() - 1;
        char c;
        length -= 3;
        while (length > 0) {
            c = str.charAt(i1);
            length--;
            buf1.append(c);
            i1++;

            if (length > 0 ) {
                c = str.charAt(i2);
                length--;
                buf2.insert(0, c);
                i2--;
            }
        }
        buf1.append("...");
        buf1.append(buf2);
        return buf1.toString();
    }

    /**
     * 如果指定字符串长度超过了最大长度，则对指定的字符串做截断处理
     * @param text 字符串
     * @param maxLength 最大长度
     * @return 返回长度不超过 maxLength 的字段串
     */
    public static String truncation(String text, int maxLength) {
        if (text != null && text.length() > maxLength) {
            return text.substring(0, maxLength);
        }
        return text;
    }

    /**
     * Returns a string, of length at least {@code minLength}, consisting of {@code string} prepended
     * with as many copies of {@code padChar} as are necessary to reach that length. For example,
     *
     * <ul>
     *   <li>{@code padStart("7", 3, '0')} returns {@code "007"}
     *   <li>{@code padStart("2010", 3, '0')} returns {@code "2010"}
     * </ul>
     *
     * <p>See {@link java.util.Formatter} for a richer set of formatting capabilities.
     *
     * @param string the string which should appear at the end of the result
     * @param minLength the minimum length the resulting string must have. Can be zero or negative, in
     *     which case the input string is always returned.
     * @param padChar the character to insert at the beginning of the result until the minimum length
     *     is reached
     * @return the padded string
     */
    public static String padStart(String string, int minLength, char padChar) {
        Checks.verifyNotNull(string, "string");
        if (string.length() >= minLength) {
            return string;
        }
        StringBuilder sb = new StringBuilder(minLength);
        for (int i = string.length(); i < minLength; i++) {
            sb.append(padChar);
        }
        sb.append(string);
        return sb.toString();
    }

    /**
     * Returns a string, of length at least {@code minLength}, consisting of {@code string} appended
     * with as many copies of {@code padChar} as are necessary to reach that length. For example,
     *
     * <ul>
     *   <li>{@code padEnd("4.", 5, '0')} returns {@code "4.000"}
     *   <li>{@code padEnd("2010", 3, '!')} returns {@code "2010"}
     * </ul>
     *
     * <p>See {@link java.util.Formatter} for a richer set of formatting capabilities.
     *
     * @param string the string which should appear at the beginning of the result
     * @param minLength the minimum length the resulting string must have. Can be zero or negative, in
     *     which case the input string is always returned.
     * @param padChar the character to append to the end of the result until the minimum length is
     *     reached
     * @return the padded string
     */
    public static String padEnd(String string, int minLength, char padChar) {
        Checks.verifyNotNull(string, "string");
        if (string.length() >= minLength) {
            return string;
        }
        StringBuilder sb = new StringBuilder(minLength);
        sb.append(string);
        for (int i = string.length(); i < minLength; i++) {
            sb.append(padChar);
        }
        return sb.toString();
    }

    /**
     * Returns a string consisting of a specific number of concatenated copies of an input string. For
     * example, {@code repeat("hey", 3)} returns the string {@code "heyheyhey"}.
     *
     * <p><b>Java 11+ users:</b> use {@code string.repeat(count)} instead.
     *
     * @param string any non-null string
     * @param count the number of times to repeat it; a nonnegative integer
     * @return a string containing {@code string} repeated {@code count} times (the empty string if
     *     {@code count} is zero)
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public static String repeat(String string, int count) {
        Checks.verifyNotNull(string, "string");

        if (count <= 1) {
            return (count == 0) ? "" : string;
        }

        final int len = string.length();
        final long longSize = (long) len * (long) count;
        final int size = (int) longSize;
        if (size != longSize) {
            throw new ArrayIndexOutOfBoundsException("Required array size too large: " + longSize);
        }

        final char[] array = new char[size];
        string.getChars(0, len, array, 0);
        int n;
        for (n = len; n < size - n; n <<= 1) {
            System.arraycopy(array, 0, array, n, n);
        }
        System.arraycopy(array, 0, array, n, size - n);
        return new String(array);
    }

    public static String nullToEmpty(String string) {
        return string == null ? "" : string;
    }

    public static String emptyToNull(String string) {
        return string == null || string.isEmpty() ? null : string;
    }

    /**
     * 将容器中的元素以 separator 分隔进行拼接
     * @param collection 要拼接的数据
     * @param separator 分隔符
     * @return 返回拼接后的字符串
     */
    public static String join(Collection<?> collection, String separator) {
        return join(collection.toArray(), separator);
    }

    /**
     * 将容器中的元素以 separator 分隔进行拼接
     * @param array 要拼接的数据
     * @param separator 分隔符
     * @return 返回拼接后的字符串
     */
    public static String join(Object[] array, String separator) {
        if (array == null || array.length == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(array[0]);
        for (int i = 1; i < array.length; i++) {
            sb.append(separator).append(array[i]);
        }
        return sb.toString();
    }

    /**
     * 解码 unicode 编码字符串
     * @param unicode unicode编码的字符串
     * @return 返回解码后的字符串
     */
    public static String fromUnicode(String unicode) {
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(unicode);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            unicode = unicode.replace(matcher.group(1), ch + "");
        }
        return unicode;
    }

    /**
     * 转为 unicode 编码字符串
     * @param input 要编码的字符串
     * @return 返回编码后的字符串
     */
    public static String toUnicode(String input) {
        StringBuilder builder = new StringBuilder();
        for (char ch : input.toCharArray()) {
            builder.append(String.format("\\u%04x", (int) ch));
        }
        return builder.toString();
    }

    /**
     * 将指定的字符串转换成适合HTML格式的字符串。
     */
    public static String stringToHTMLString(String text) {
        StringBuilder sb = new StringBuilder(text.length());
        // true if last char was blank
        boolean lastWasBlankChar = false;
        int len = text.length();
        char c;

        for (int i = 0; i < len; i++) {
            c = text.charAt(i);
            if (c == ' ') {
                // blank gets extra work,
                // this solves the problem you get if you replace all
                // blanks with &nbsp;, if you do that you loss
                // word breaking
                if (lastWasBlankChar) {
                    lastWasBlankChar = false;
                    sb.append("&nbsp;");
                } else {
                    lastWasBlankChar = true;
                    sb.append(' ');
                }
            } else {
                lastWasBlankChar = false;
                //
                // HTML Special Chars
                if (c == '"') {
                    sb.append("&quot;");
                } else if (c == '&') {
                    sb.append("&amp;");
                } else if (c == '<') {
                    sb.append("&lt;");
                } else if (c == '>') {
                    sb.append("&gt;");
                } else if (c == '\n') {
                    // Handle Newline
                    sb.append("&lt;br/&gt;");
                } else {
                    int ci = 0xffff & c;
                    if (ci < 160) {
                        // nothing special only 7 Bit
                        sb.append(c);
                    } else {
                        // Not 7 Bit use the unicode system
                        sb.append("&#");
                        sb.append(ci);
                        sb.append(';');
                    }
                }
            }
        }
        return sb.toString();
    }

    /**
     * 获取自增型文件名。
     * 如：
     * a.txt --> a_1.txt
     * b_5.txt --> b_6.txt
     * c_2 --> c_3
     */
    public static String autoincrementFilename(String filename) {
        String s1;
        String s2;
        String suffix;
        String tmp;
        int m = filename.lastIndexOf('.');
        if (m >= 0) {
            tmp = filename.substring(0, m);
            suffix = filename.substring(m);
        } else {
            tmp = filename;
            suffix = "";
        }
        int n = tmp.lastIndexOf('_');
        if (n >= 0) {
            s1 = tmp.substring(0, n);
            s2 = tmp.substring(n+1);
        } else {
            s1 = tmp;
            s2 = "";
        }
        int i = 1;
        if (s2.matches("^[1-9]\\d*$")) {
            try {
                i = Integer.parseInt(s2);
            } catch (Exception ex) {
                // ignore
            }
            i++;
            return s1 + "_" + i + suffix;
        }
        if (m != -1) {
            return tmp + "_"+ i + suffix;
        }
        return filename + "_"+ i;
    }

    /**
     * 比较version1和version2，如果version1小于、等于、大于version2分别返回 -1、0、1
     * @param version1 版本号1
     * @param version2 版本号2
     * @return 比较version1和version2，如果version1小于、等于、大于version2分别返回 -1、0、1
     */
    public static int versionCompare(String version1, String version2) {
        if (Checks.isBlank(version1)) {
            return Checks.isBlank(version2) ? 0 : -1;
        } else if (Checks.isBlank(version2)) {
            return 1;
        }

        StringTokenizer t1 = new StringTokenizer(version1, "._");
        StringTokenizer t2 = new StringTokenizer(version2, "._");
        while (true) {
            int n1 = 0;
            int n2 = 0;
            int c = 2;
            if (t1.hasMoreTokens()) {
                n1 = Integer.parseInt(t1.nextToken());
            } else {
                c--;
            }
            if (t2.hasMoreTokens()) {
                n2 = Integer.parseInt(t2.nextToken());
            } else {
                c--;
            }
            if (c == 0) {
                break;
            }
            int d = Integer.compare(n1, n2);
            if (d != 0) {
                return d;
            }
        }
        return 0;
    }
}
